﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Disposables;
using System.Linq;
using System.Linq.Functional;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using SoftSource.Common;
using SoftSource.MVVMC;
using SoftSource.Reactive;
using SoftSource.Timecard.Entry.ViewModelInterfaces;
using SoftSource.Timecard.Web.Data;

namespace SoftSource.Timecard.Entry.Views
{
    public partial class EntryView : UserControl, IDisposable, IView<IEntryViewModelConsume>
    {
        #region Fields
        private CompositeDisposable compositeDisposable;
        private Filter<EntryLog> entryFilter;
        #endregion Fields

        public EntryView()
        {
            InitializeComponent();

            this.compositeDisposable = new CompositeDisposable();

            Loaded += new System.Windows.RoutedEventHandler(EntryView_Loaded);
        }

        #region Events

        private void EntryView_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this.ViewModel != null)
            {
                this.AttatchFilterToEntryChanges();                
                this.ResetDateRangeSelector();
                this.SubscribeToSourceCollectionChanges();
                this.LoadViewModelData();
            }
        }

        private void EntryDataGrid_RowEditEnded(object sender, DataGridRowEditEndedEventArgs e)
        {
            if (e.EditAction == DataGridEditAction.Commit)
            {
                EntryLog entry = e.Row.DataContext as EntryLog;

                if (entry != null)
                {
                    var saveObservable = this.ViewModel.Save(entry);
                    saveObservable.Subscribe();
                }
            }
        }

        private void Refresh_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            this.Busy.BusyContent = "Loading...";
            this.Busy.IsBusy = true;

            this.ViewModel
                .LoadEntries(this.EntryDateRangeSelector.StartDate, this.EntryDateRangeSelector.EndDate)
                .Subscribe(unit => this.Busy.IsBusy = false);
        }

        private void Delete_Click(object sender, RoutedEventArgs e)
        {
            EntryLog entry = this.GetEntryFromFrameworkElement(sender);
            bool delete = this.CanModifyEntry(entry);

            if (delete)
                this.TryDelete(entry);
        }

        private void Edit_Click(object sender, RoutedEventArgs e)
        {
            EntryLog entry = this.GetEntryFromFrameworkElement(sender);
            bool edit = this.CanModifyEntry(entry);

            if (edit)
                this.OpenEditor();
        }

        private void Insert_Click(object sender, RoutedEventArgs e)
        {
            this.EntryDataGrid.CommitEdit();

            if (this.EntryDataGrid.SelectedItems.Count > 0)
            {
                this.Insert();
            }
            else
            {
                var entry = this.ViewModel.AddNew(null);
                this.entryFilter.Refresh();
                this.ViewModel.SaveAll().Subscribe();

                if (entry.EntryDate > this.EntryDateRangeSelector.EndDate)
                {
                    this.EntryDateRangeSelector.EndDate = entry.EntryDate.AddDays(1);
                }
            }
        }

        private void EntryDataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
        {
            var entry = e.Row.DataContext as EntryLog;

            bool edit = this.CanModifyEntry(entry);

            e.Cancel = !edit;            
        }

        #endregion Events

        #region Methods

        private void AttatchFilterToEntryChanges()
        {
            var entriesChangedDisposable = this.ViewModel.Entries
                .Do(pcv => this.entryFilter = pcv.AttachFilter<EntryLog>())
                .Do(pcv => this.entryFilter.Add(entry => entry.EntryDate >= this.EntryDateRangeSelector.StartDate && entry.EntryDate <= this.EntryDateRangeSelector.EndDate))
                .Do(pcv => this.EntryDataGrid.Focus())
                .SubscribeOnDispatcher()
                .Subscribe();

            this.compositeDisposable.Add(entriesChangedDisposable);
        }

        private void ResetDateRangeSelector()
        {
            var lastWeek = DateTime.Now.AddDays(-7);
            var now = lastWeek.AddMonths(1);

            this.EntryDateRangeSelector.StartDate = lastWeek;
            this.EntryDateRangeSelector.EndDate = now;
        }

        private void LoadViewModelData()
        {
            this.ViewModel.Load // Load the data required to use this ViewModel
                .Nullable()
                .SelectMany(this.ViewModel.LoadEntries(this.EntryDateRangeSelector.StartDate, this.EntryDateRangeSelector.EndDate))
                .Do(unit => this.DataLoaded())
                .Do(unit => this.ViewModel.Entries.Value
                    .Cast<EntryLog>()
                    .Run(entry => this.SubscribeToEntry(entry)))
                .Do(unit => this.SubscribeToEntryCollection(this.ViewModel.Entries.Value.WhenCollectionChanged()))
                .Do(unit => System.Windows.Browser.HtmlPage.Plugin.Focus()) // Focus the Silverlight plugin
                .Do(unit => this.EntryDataGrid.Focus()) // Focus the datagrid
                .SubscribeOnDispatcher()
                .Subscribe();
        }

        private void SubscribeToEntryCollection(IObservable<IEvent<NotifyCollectionChangedEventArgs>> observable)
        {
            var collectionChangedDisposable = observable
                .Select(evnt => evnt.EventArgs)
                .Where(args => args.Action == NotifyCollectionChangedAction.Add)
                .Do(args => args.NewItems
                    .Cast<EntryLog>()
                    .Run(this.SubscribeToEntry))
                .Subscribe();

            this.compositeDisposable.Add(collectionChangedDisposable);
        }

        private void SubscribeToSourceCollectionChanges()
        {
            var whenSourceCollectionChanged = Observable.Empty<IEvent<NotifyCollectionChangedEventArgs>>();            

            this.ViewModel.Entries
                .Do(pcv =>
                    {
                        if (this.ViewModel.Entries.Value.SourceCollection is INotifyCollectionChanged)
                        {
                            var collectionChanged = (INotifyCollectionChanged)this.ViewModel.Entries.Value.SourceCollection;
                            whenSourceCollectionChanged = collectionChanged.WhenCollectionChanged();
                        }
                        else
                        {
                            whenSourceCollectionChanged = Observable.Empty<IEvent<NotifyCollectionChangedEventArgs>>();
                        }

                        this.SubscribeToEntryCollection(whenSourceCollectionChanged);
                    })
                .Subscribe();
        }

        private void SubscribeToEntry(EntryLog entry)
        {
            this.compositeDisposable.Add(entry.WhenPropertyChanged()
                .Where(evnt => evnt.EventArgs.PropertyName == ObjectReflection.GetPropertyName(() => entry.ProjectID))
                .Do(evnt => entry.CategoryID = App.Model.ProjectSummaries.Entities
                    .Single(p => p.ProjectID == entry.ProjectID).ProjectCategorySummaries
                    .OrderBy(pc => pc.CategorySummary.Name)
                    .First().CategoryID)
                .Subscribe());
        }

        private void DataLoaded()
        {
            var keyDownDisposable = Observable.FromEvent<KeyEventHandler, KeyEventArgs>(
                handler => (sender, e) => handler(sender, e),
                handler => this.KeyDown += handler,
                handler => this.KeyDown -= handler)
                .SubscribeOnDispatcher()
                .Select(evnt => evnt.EventArgs.Key)
                .Select(key => key.Match()
                    .With<Action>(k => k == Key.Insert || k == Key.Add, this.Insert)
                    .With(k => k == Key.Delete || k == Key.Subtract, () => this.TryDelete(this.EntryDataGrid.SelectedItem as EntryLog))
                    .Else(k => null)
                    .Do())
                .Where(action => action != null)
                .Do(action => action())
                .Do(action => this.entryFilter.Refresh())                
                .ToUnit()
                .Subscribe();

            this.compositeDisposable.Add(keyDownDisposable);
        }

        private void Insert()
        {
            this.EntryDataGrid.CommitEdit();

            int dayIncrement = 1;

            if (this.EntryDataGrid.SelectedItems.Count == 1)
            {
                var currentEntry = this.EntryDataGrid.SelectedItem as EntryLog;
                var cloned = this.ViewModel.AddNew(currentEntry);
            }
            else
            {
                dayIncrement = 7;

                var cloned = this.EntryDataGrid.SelectedItems
                    .Cast<EntryLog>()
                    .Select(this.ViewModel.Clone)
                    .Do(entry => entry.EntryDate = entry.EntryDate.AddDays(dayIncrement))
                    .MemoizeAll();
                
                cloned.Run();

                var latestEntry = cloned.OrderByDescending(entry => entry.EntryDate).First();

                if (latestEntry.EntryDate > this.EntryDateRangeSelector.EndDate)
                    this.EntryDateRangeSelector.EndDate = latestEntry.EntryDate.AddDays(1);
            }

            this.ViewModel.SaveAll().Subscribe();

            this.entryFilter.Refresh.TryInvoke();
        }

        private void TryDelete(EntryLog entry)
        {
            if (entry != null)
            {
                if (entry.Duration > 0)
                {
                    var result = MessageBox.Show("Are you sure you want to delete this entry?", "Caution", MessageBoxButton.OKCancel);

                    if (result != MessageBoxResult.OK)
                        return;
                }

                this.EntryDataGrid.CommitEdit();
                this.ViewModel
                    .Delete(entry)
                    .Subscribe();
            }
        }

        private void OpenEditor()
        {
            ChildWindow cw = new ChildWindow();

            UIElement ui = this.ViewModel.CreateEditEntryEditorView(this.EntryDataGrid.SelectedItem as EntryLog, () =>
                {
                    cw.Close();
                    this.EntryDataGrid.Focus();
                });
            cw.Content = ui;

            cw.Show();
            cw.Focus();
        }

        private EntryLog GetEntryFromFrameworkElement(object frameworkElement)
        {
            FrameworkElement fe = frameworkElement as FrameworkElement;

            if (fe == null)
                return null;
            
            var entry = fe.DataContext as EntryLog;
            
            return entry;
        }

        private bool CanModifyEntry(EntryLog entry)
        {
            if (entry == null)
                return false;

            bool modify = false;

            if (WebContext.Current.User.IsInRole("Administrator"))
                modify = true;
            else if (entry.BillingStateID == BillingState.NotApproved)
                modify = true;
            else
                modify = false;

            return modify;
        }

        #endregion Methods

        #region IView<IEntryViewModelConsume> Members

        public IEntryViewModelConsume ViewModel
        {
            get { return this.DataContext as IEntryViewModelConsume; }
            set { this.DataContext = value; }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.compositeDisposable.Dispose();
            GC.SuppressFinalize(this);
        }

        #endregion

        ~EntryView()
        {
            this.Dispose();
        }
    }
}
